 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.actions;

 import java.lang.reflect.InvocationTargetException ;
 import java.util.ArrayList ;
 import java.util.Iterator ;
 import java.util.List ;

 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.WorkspaceJob;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.OperationCanceledException;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubProgressMonitor;
 import org.eclipse.core.runtime.jobs.ISchedulingRule;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
 import org.eclipse.ui.internal.ide.StatusUtil;
 import org.eclipse.ui.internal.progress.ProgressMonitorJobsDialog;

 /**
  * The abstract superclass for actions which invoke commands implemented in
  * org.eclipse.core.* on a set of selected resources.
  *
  * It iterates over all selected resources; errors are collected and displayed
  * to the user via a problems dialog at the end of the operation. User requests
  * to cancel the operation are passed along to the core.
  * <p>
  * Subclasses must implement the following methods:
  * <ul>
  * <li><code>invokeOperation</code> - to perform the operation on one of the
  * selected resources</li>
  * <li><code>getOperationMessage</code> - to furnish a title for the progress
  * dialog</li>
  * </ul>
  * </p>
  * <p>
  * Subclasses may override the following methods:
  * <ul>
  * <li><code>shouldPerformResourcePruning</code> - reimplement to turn off</li>
  * <li><code>updateSelection</code> - extend to refine enablement criteria</li>
  * <li><code>getProblemsTitle</code> - reimplement to furnish a title for the
  * problems dialog</li>
  * <li><code>getProblemsMessage</code> - reimplement to furnish a message for
  * the problems dialog</li>
  * <li><code>run</code> - extend to </li>
  * </ul>
  * </p>
  */
 public abstract class WorkspaceAction extends SelectionListenerAction {
     /**
      * The shell in which to show the progress and problems dialog.
      */
     private final Shell shell;

     /**
      * Creates a new action with the given text.
      *
      * @param shell
      * the shell (for the modal progress dialog and error messages)
      * @param text
      * the string used as the text for the action, or
      * <code>null</code> if there is no text
      */
     protected WorkspaceAction(Shell shell, String text) {
         super(text);
         if (shell == null) {
             throw new IllegalArgumentException ();
         }
         this.shell = shell;
     }

     /**
      * Opens an error dialog to display the given message.
      * <p>
      * Note that this method must be called from UI thread.
      * </p>
      *
      * @param message
      * the message
      */
     void displayError(String message) {
         if (message == null) {
             message = IDEWorkbenchMessages.WorkbenchAction_internalError;
         }
         MessageDialog.openError(shell, getProblemsTitle(), message);
     }

     /**
      * Runs <code>invokeOperation</code> on each of the selected resources,
      * reporting progress and fielding cancel requests from the given progress
      * monitor.
      * <p>
      * Note that if an action is running in the background, the same action
      * instance can be executed multiple times concurrently. This method must
      * not access or modify any mutable state on action class.
      *
      * @param monitor
      * a progress monitor
      * @return The result of the execution
      */
     final IStatus execute(List resources, IProgressMonitor monitor) {
         MultiStatus errors = null;
         // 1FTIMQN: ITPCORE:WIN - clients required to do too much iteration work
 if (shouldPerformResourcePruning()) {
             resources = pruneResources(resources);
         }
         // 1FV0B3Y: ITPUI:ALL - sub progress monitors granularity issues
 monitor.beginTask("", resources.size() * 1000); //$NON-NLS-1$
 // Fix for bug 31768 - Don't provide a task name in beginTask
 // as it will be appended to each subTask message. Need to
 // call setTaskName as its the only was to assure the task name is
 // set in the monitor (see bug 31824)
 monitor.setTaskName(getOperationMessage());
         Iterator resourcesEnum = resources.iterator();
         try {
             while (resourcesEnum.hasNext()) {
                 IResource resource = (IResource) resourcesEnum.next();
                 try {
                     // 1FV0B3Y: ITPUI:ALL - sub progress monitors granularity
 // issues
 invokeOperation(resource, new SubProgressMonitor(monitor,
                             1000));
                 } catch (CoreException e) {
                     errors = recordError(errors, e);
                 }
                 if (monitor.isCanceled()) {
                     throw new OperationCanceledException();
                 }
             }
             return errors == null ? Status.OK_STATUS : errors;
         } finally {
             monitor.done();
         }
     }

     /**
      * Returns the string to display for this action's operation.
      * <p>
      * Note that this hook method is invoked in a non-UI thread.
      * </p>
      * <p>
      * Subclasses must implement this method.
      * </p>
      *
      * @return the message
      *
      * @since 3.1
      */
     protected abstract String getOperationMessage();

     /**
      * Returns the string to display for this action's problems dialog.
      * <p>
      * The <code>WorkspaceAction</code> implementation of this method returns
      * a vague message (localized counterpart of something like "The following
      * problems occurred."). Subclasses may reimplement to provide something
      * more suited to the particular action.
      * </p>
      *
      * @return the problems message
      *
      * @since 3.1
      */
     protected String getProblemsMessage() {
         return IDEWorkbenchMessages.WorkbenchAction_problemsMessage;
     }

     /**
      * Returns the title for this action's problems dialog.
      * <p>
      * The <code>WorkspaceAction</code> implementation of this method returns
      * a generic title (localized counterpart of "Problems"). Subclasses may
      * reimplement to provide something more suited to the particular action.
      * </p>
      *
      * @return the problems dialog title
      *
      * @since 3.1
      */
     protected String getProblemsTitle() {
         return IDEWorkbenchMessages.WorkspaceAction_problemsTitle;
     }

     /**
      * Returns the shell for this action. This shell is used for the modal
      * progress and error dialogs.
      *
      * @return the shell
      */
     Shell getShell() {
         return shell;
     }

     /**
      * Performs this action's operation on each of the selected resources,
      * reporting progress to, and fielding cancel requests from, the given
      * progress monitor.
      * <p>
      * Note that this method is invoked in a non-UI thread.
      * </p>
      * <p>
      * Subclasses must implement this method.
      * <p>
      * @deprecated Since 3.3, subclasses should instead implement the method
      * {@link #createOperation(IStatus[])} and provide an empty implementation
      * for this method.
      * </p>
      *
      * @param resource
      * one of the selected resources
      * @param monitor
      * a progress monitor
      * @exception CoreException
      * if the operation fails
      *
      * @since 3.1
      */
     protected abstract void invokeOperation(IResource resource,
             IProgressMonitor monitor) throws CoreException;

     /**
      * Returns whether the given resource is a descendent of any of the
      * resources in the given list.
      *
      * @param resources
      * the list of resources (element type: <code>IResource</code>)
      * @param child
      * the resource to check
      * @return <code>true</code> if <code>child</code> is a descendent of
      * any of the elements of <code>resources</code>
      */
     boolean isDescendent(List resources, IResource child) {
         IResource parent = child.getParent();
         return parent != null
                 && (resources.contains(parent) || isDescendent(resources,
                         parent));
     }

     /**
      * Performs pruning on the given list of resources, as described in
      * <code>shouldPerformResourcePruning</code>.
      *
      * @param resourceCollection
      * the list of resources (element type: <code>IResource</code>)
      * @return the list of resources (element type: <code>IResource</code>)
      * after pruning.
      * @see #shouldPerformResourcePruning
      */
     List pruneResources(List resourceCollection) {
         List prunedList = new ArrayList (resourceCollection);
         Iterator elementsEnum = prunedList.iterator();
         while (elementsEnum.hasNext()) {
             IResource currentResource = (IResource) elementsEnum.next();
             if (isDescendent(prunedList, currentResource)) {
                 elementsEnum.remove(); // Removes currentResource
 }
         }
         return prunedList;
     }

     /**
      * Records the core exception to be displayed to the user once the action is
      * finished.
      *
      * @param error
      * a <code>CoreException</code>
      */
     MultiStatus recordError(MultiStatus errors, CoreException error) {
         if (errors == null) {
             errors = new MultiStatus(IDEWorkbenchPlugin.IDE_WORKBENCH,
                     IStatus.ERROR, getProblemsMessage(), null);
         }
         errors.merge(error.getStatus());
         return errors;
     }

     /**
      * The <code>CoreWrapperAction</code> implementation of this
      * <code>IAction</code> method uses a <code>ProgressMonitorDialog</code>
      * to run the operation. The operation calls <code>execute</code> (which,
      * in turn, calls <code>invokeOperation</code>). Afterwards, any
      * <code>CoreException</code>s encountered while running the operation
      * are reported to the user via a problems dialog.
      * <p>
      * Subclasses may extend this method.
      * </p>
      */
     public void run() {
         IStatus[] errorStatus = new IStatus[1];
         try {
             new ProgressMonitorJobsDialog(shell).run(true, true,
                     createOperation(errorStatus));
         } catch (InterruptedException e) {
             return;
         } catch (InvocationTargetException e) {
             // we catch ExecutionException in the created operation, but unexpected runtime
 // exceptions or errors may still occur
 String msg = NLS.bind(
                     IDEWorkbenchMessages.WorkspaceAction_logTitle, getClass()
                             .getName(), e.getTargetException());
             IDEWorkbenchPlugin.log(msg, StatusUtil.newStatus(IStatus.ERROR,
                     msg, e.getTargetException()));
             displayError(e.getTargetException().getMessage());
         }
         // If errors occurred, open an Error dialog & build a multi status error
 // for it
 if (errorStatus[0] != null && !errorStatus[0].isOK()) {
             ErrorDialog.openError(shell, getProblemsTitle(), null, // no
 // special
 // message
 errorStatus[0]);
         }
     }

     /**
      * Returns whether this action should attempt to optimize the resources
      * being operated on. This kind of pruning makes sense when the operation
      * has depth infinity semantics (when the operation is applied explicitly to
      * a resource then it is also applied implicitly to all the resource's
      * descendents).
      * <p>
      * The <code>WorkspaceAction</code> implementation of this method returns
      * <code>true</code>. Subclasses should reimplement to return
      * <code>false</code> if pruning is not required.
      * </p>
      *
      * @return <code>true</code> if pruning should be performed, and
      * <code>false</code> if pruning is not desired
      *
      * @since 3.1
      */
     protected boolean shouldPerformResourcePruning() {
         return true;
     }

     /**
      * The <code>WorkspaceAction</code> implementation of this
      * <code>SelectionListenerAction</code> method ensures that this action is
      * disabled if any of the selected resources are inaccessible. Subclasses
      * may extend to react to selection changes; however, if the super method
      * returns <code>false</code>, the overriding method should also return
      * <code>false</code>.
      */
     protected boolean updateSelection(IStructuredSelection selection) {
         if (!super.updateSelection(selection) || selection.isEmpty()) {
             return false;
         }
         for (Iterator i = getSelectedResources().iterator(); i.hasNext();) {
             IResource r = (IResource) i.next();
             if (!r.isAccessible()) {
                 return false;
             }
         }
         return true;
     }

     /**
      * Returns the elements that the action is to be performed on. By default
      * return the selected resources.
      * <p>
      * Subclasses may override this method.
      *
      * @return list of resource elements (element type: <code>IResource</code>)
      */
     protected List getActionResources() {
         return getSelectedResources();
     }

     /**
      * Run the action in the background rather than with the progress dialog.
      *
      * @param rule
      * The rule to apply to the background job or <code>null</code>
      * if there isn't one.
      */
     public void runInBackground(ISchedulingRule rule) {
         runInBackground(rule, (Object []) null);
     }

     /**
      * Run the action in the background rather than with the progress dialog.
      *
      * @param rule
      * The rule to apply to the background job or <code>null</code>
      * if there isn't one.
      * @param jobFamily
      * a single family that the job should belong to or
      * <code>null</code> if none.
      *
      * @since 3.1
      */
     public void runInBackground(ISchedulingRule rule, Object jobFamily) {
         if (jobFamily == null) {
             runInBackground(rule, (Object []) null);
         } else {
             runInBackground(rule, new Object [] { jobFamily });
         }
     }

     /**
      * Run the action in the background rather than with the progress dialog.
      *
      * @param rule
      * The rule to apply to the background job or <code>null</code>
      * if there isn't one.
      * @param jobFamilies
      * the families the job should belong to or <code>null</code>
      * if none.
      *
      * @since 3.1
      */
     public void runInBackground(ISchedulingRule rule, final Object [] jobFamilies) {
         // obtain a copy of the selected resources before the job is forked
 final List resources = new ArrayList (getActionResources());
         Job job = new WorkspaceJob(removeMnemonics(getText())) {

             /*
              * (non-Javadoc)
              *
              * @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
              */
             public boolean belongsTo(Object family) {
                 if (jobFamilies == null || family == null) {
                     return false;
                 }
                 for (int i = 0; i < jobFamilies.length; i++) {
                     if (family.equals(jobFamilies[i])) {
                         return true;
                     }
                 }
                 return false;
             }

             /*
              * (non-Javadoc)
              *
              * @see org.eclipse.core.resources.WorkspaceJob#runInWorkspace(org.eclipse.core.runtime.IProgressMonitor)
              */
             public IStatus runInWorkspace(IProgressMonitor monitor) {
                 return WorkspaceAction.this.execute(resources, monitor);
             }
         };
         if (rule != null) {
             job.setRule(rule);
         }
         job.setUser(true);
         job.schedule();
     }

     /**
      * Returns the operation to perform when this action runs. The returned
      * operation must be an {@link IRunnableWithProgress} that will perform the
      * action's work. The default implementation returns an operation that will
      * iterate over the selected resources and call
      * {@link #invokeOperation(IResource, IProgressMonitor)} for each resource.
      * Subclasses must either implement
      * {@link #invokeOperation(IResource, IProgressMonitor)} or override this
      * method to provide a different operation. Subclasses typically override
      * this method when an undoable operation is to be provided.
      *
      * @param errorStatus
      * an array of error status objects to which the result of
      * running the operation should be added.
      *
      * @return the operation to perform when this action runs.
      * @since 3.3
      */
     protected IRunnableWithProgress createOperation(final IStatus[] errorStatus) {
         return new WorkspaceModifyOperation() {
             public void execute(IProgressMonitor monitor) {
                 errorStatus[0] = WorkspaceAction.this.execute(
                         getActionResources(), monitor);
             }
         };
     }

 }

